﻿
using System;
using System.Windows.Media;

namespace Red.Core
{
    /// <summary>
    ///     Bunch of methods to operate on colors
    /// </summary>
    public static class ColorHelpers
    {
        /// <summary>
        ///     Creates RGB color from HSL values.
        /// </summary>
        /// <param name="h">Hue value [0-360]</param>
        /// <param name="s">Saturation value [0-1]</param>
        /// <param name="l">Lightness value [0-1]</param>
        public static Color HslToColor( float h, float s, float l, float a = 1f )
        {
            h /= 360f;
            float r, g, b;

            if ( s == 0f )
            {
                r = g = b = l;
            }
            else
            {
                var q = l < 0.5f ? l * ( 1f + s ) : l + s - l * s;
                var p = 2f * l - q;
                r = HueToRgb( p, q, h + 1f / 3f );
                g = HueToRgb( p, q, h );
                b = HueToRgb( p, q, h - 1f / 3f );
            }

            return Color.FromScRgb( a, r, g, b );
        }

        /// <summary>
        ///     Converts color into HSL values
        /// </summary>
        /// <param name="color"></param>
        /// <param name="h">Hue value [0-360]</param>
        /// <param name="s">Saturation value [0-1]</param>
        /// <param name="l">Lightness value [0-1]</param>
        public static void ColorToHsl( Color color, out float h, out float s, out float l )
        {
            float r = color.ScR;
            float g = color.ScG;
            float b = color.ScB;

            float max = ( r > g && r > b ) ? r : ( g > b ) ? g : b;
            float min = ( r < g && r < b ) ? r : ( g < b ) ? g : b;

            h = s = l = ( max + min ) / 2f;

            if ( max == min )
            {
                h = s = 0f;
            }
            else
            {
                float d = max - min;
                s = ( l > 0.5f ) ? d / ( 2f - max - min ) : d / ( max + min );

                if ( r > g && r > b )
                    h = ( g - b ) / d + ( g < b ? 6f : 0f );
                else if ( g > b )
                    h = ( b - r ) / d + 2f;
                else
                    h = ( r - g ) / d + 4f;

                h *= 60f;
            }
        }

        /// <summary>
        ///     Creates RGB color from HSV values
        /// </summary>
        /// <param name="h">Hue value [0-360]</param>
        /// <param name="s">Saturation value [0-1]</param>
        /// <param name="v">Value value [0-1]</param>
        public static Color HsvToColor( float h, float s, float v, float a = 1f )
        {
            if ( s == 0f )
            {
                return Color.FromScRgb( a, v, v, v );
            }
            else
            {
                int hi = Convert.ToInt32( System.Math.Floor( h / 60f ) ) % 6;
                float f = h / 60f - (float)System.Math.Floor( h / 60f );

                float p = v * ( 1f - s );
                float q = v * ( 1f - f * s );
                float t = v * ( 1f - ( 1f - f ) * s );

                if ( hi == 0 )
                    return Color.FromScRgb( a, v, t, p );
                else if ( hi == 1 )
                    return Color.FromScRgb( a, q, v, p );
                else if ( hi == 2 )
                    return Color.FromScRgb( a, p, v, t );
                else if ( hi == 3 )
                    return Color.FromScRgb( a, p, q, v );
                else if ( hi == 4 )
                    return Color.FromScRgb( a, t, p, v );
                else
                    return Color.FromScRgb( a, v, p, q );
            }
        }

        /// <summary>
        ///     Converts color into HSV values
        /// </summary>
        /// <param name="color"></param>
        /// <param name="h">Hue value [0-360]</param>
        /// <param name="s">Saturation value [0-1]</param>
        /// <param name="v">Value value [0-1]</param>
        public static void ColorToHsv( Color color, out float h, out float s, out float v )
        {
            float min = System.Math.Min( color.ScR, System.Math.Min( color.ScG, color.ScB ) );
            float max = System.Math.Max( color.ScR, System.Math.Max( color.ScG, color.ScB ) );

            float del = max - min;
            v = max;
            h = s = 0f;

            if ( del != 0f )
            {
               s = del / max;
               float dR = ( ( ( max - color.ScR ) / 6f ) + ( del / 2f ) ) / del;
               float dG = ( ( ( max - color.ScG ) / 6f ) + ( del / 2f ) ) / del;
               float dB = ( ( ( max - color.ScB ) / 6f ) + ( del / 2f ) ) / del;

               if ( color.ScR == max ) 
                   h = dB - dG;
               else if ( color.ScG == max ) 
                   h = ( 1f / 3f ) + dR - dB;
               else if ( color.ScB == max ) 
                   h = ( 2f / 3f ) + dG - dR;

               if ( h < 0f ) h += 1f;
               if ( h > 1f ) h -= 1f;
               h *= 360f;
            }
        }

        private static float HueToRgb( float p, float q, float t )
        {
            if ( t < 0.0f ) t += 1.0f;
            if ( t > 1.0f ) t -= 1.0f;
            if ( t < 1.0f / 6.0f ) return p + ( q - p ) * 6.0f * t;
            if ( t < 1.0f / 2.0f ) return q;
            if ( t < 2.0f / 3.0f ) return p + ( q - p ) * ( 2.0f / 3.0f - t ) * 6.0f;
            return p;
        }

        /// <summary>
        ///     Linear interpolation of two colors
        /// </summary>
        /// <param name="A">First color</param>
        /// <param name="B">Second color</param>
        /// <param name="x">Interpolation factor</param>
        /// <returns></returns>
        public static Color InterpolateColors( Color A, Color B, float x )
        {
            var finalRed = x * B.R + (1.0f - x) * A.R;
            var finalGreen = x * B.G + (1.0f - x) * A.G;
            var finalBlue = x * B.B + (1.0f - x) * A.B;
            var finalAlpha = x * B.A + (1.0f - x) * A.A;
            return Color.FromArgb( (byte)finalAlpha, (byte)finalRed, (byte)finalGreen, (byte)finalBlue );
        }
    }
}
